// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 该文件实现了各种字段和方法查找功能。

package types

import (
	"bytes"
	"strings"
)

// LookupFieldOrMethod的内部使用：如果obj结果是与具体（非接口）类型关联的方法
// ，则该方法的签名
// 可能未完全设置。打电话给检查人员。在访问
// 方法的类型之前，objDecl（obj，nil）。

// LookupFieldOrMethod在T中查找具有给定包和名称
// 的字段或方法，并返回相应的*Var或*Func、一个索引序列和一个
// bool，指示指向
// 字段或方法的路径上是否有任何指针间接指向。如果设置了addressable，则T是可寻址
// 变量的类型（仅对方法查找有用）。T不能为零。
// 
// 最后一个索引项是找到该项的（可能嵌入的）
// 类型中的字段或方法索引，或者：
// 
// 1）命名类型的声明方法列表；或
// 2）一个接口类型的所有方法（方法集）的列表；或
// 3）结构类型的字段列表。
// 
// 前面的索引项是嵌入结构字段的索引
// 从深度0开始遍历以获取找到的项。
// 
// 如果没有找到条目，则返回一个nil对象。在这种情况下，返回的
// index和间接值具有以下含义：
// 
// If index！=nil，索引序列指向一个模棱两可的条目
// /（同一个名称在同一嵌入级别多次出现）。
// 
// -如果设置了间接，则找到了具有指针接收器类型的方法
// 但从实际接收器类型到
// 该方法的正式接收器基类型的路径上没有指针，接收器也不可寻址。
// 
func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) {
	if T == nil {
		panic("LookupFieldOrMethod on nil type")
	}

	// 方法不能与命名指针类型关联。
	// （规范：“用T表示的类型称为接收器基类型；
	// 它不能是指针或接口类型，必须在与方法相同的包中声明为
	// ”）。
	// 因此，如果我们有一个命名的指针类型，则继续使用基础的
	// 指针类型，但如果它是一个方法，则放弃结果，因为我们将
	// 没有为T找到它（另请参见第8590期）。
	if t, _ := T.(*Named); t != nil {
		if p, _ := t.Underlying().(*Pointer); p != nil {
			obj, index, indirect = lookupFieldOrMethod(p, false, pkg, name, false)
			if _, ok := obj.(*Func); ok {
				return nil, nil, false
			}
			return
		}
	}

	obj, index, indirect = lookupFieldOrMethod(T, addressable, pkg, name, false)

	// 如果我们没有找到任何内容，并且如果我们有一个具有核心类型的类型参数，
	// 请查看是否有匹配的字段（但没有方法，这些字段需要在约束中显式声明为
	// ）。如果约束是一个命名指针类型（请参见上面的
	// ），我们在这里就可以了，因为只接受字段作为结果。
	const enableTParamFieldLookup = false // 请参阅问题#51576 
	if enableTParamFieldLookup && obj == nil && isTypeParam(T) {
		if t := coreType(T); t != nil {
			obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false)
			if _, ok := obj.(*Var); !ok {
				obj, index, indirect = nil, nil, false // 只接受字段（变量）
			}
		}
	}
	return
}

// TODO（gri）下面的命名类型合并和SEED映射必须是
// 由给定类型的唯一键索引。验证命名的
// 类型始终只有一个表示（即使通过不同的包间接导入
// 时也是如此）

// lookupFieldOrMethod只能由lookupFieldOrMethod和missingMethod调用。
// 如果foldCase为true，则方法的查找将包括查找任何方法
// 该大小写折叠为与“name”相同的大小写（用于提供有用的错误消息）。
// 
// 生成的对象可能未进行完全类型检查。
func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string, foldCase bool) (obj Object, index []int, indirect bool) {
	// 警告：此函数中的代码非常微妙-不要随意修改！

	if name == "_" {
		return // 从未找到空白字段/方法
	}

	typ, isPtr := deref(T)

	// /*typ，其中typ是接口（包括类型参数），没有方法。
	if isPtr {
		if _, ok := under(typ).(*Interface); ok {
			return
		}
	}

	// 从最浅深度的单一入口开始。
	current := []embeddedType{{typ, nil, isPtr, false}}

	// 我们已经看到的命名类型，被懒洋洋地分配。
	// 用于避免递归类型的无休止搜索。
	// 由于递归类型只能使用命名类型，我们只需要跟踪这些类型。
	// （如果我们允许类型别名构造递归类型，
	// 我们必须使用类型标识而不是指针相等来进行
	// 映射键比较，就像我们在consolidateMultiples中所做的那样。）
	var seen map[*Named]bool

	// 搜索当前深度
	for len(current) > 0 {
		var next []embeddedType // 在当前深度找到嵌入类型

		// 在当前深度的所有类型中查找（pkg，name）
		for _, e := range current {
			typ := e.typ

			// 如果我们有一个命名类型，我们可能有关联的方法。
			// 先找那些。
			if named, _ := typ.(*Named); named != nil {
				if seen[named] {
					// 我们以前见过这种类型，在更浅的深度
					// （请注意，在当前深度
					// 之前，这种类型的倍数是合并的）。该深度处的类型会在当前深度处阴影
					// 这个相同的类型，所以我们可以忽略
					// 这个。
					continue
				}
				if seen == nil {
					seen = make(map[*Named]bool)
				}
				seen[named] = true

				// 寻找匹配的附加方法
				named.resolve(nil)
				if i, m := named.lookupMethod(pkg, name, foldCase); m != nil {
					// 潜在匹配
					// 注意：方法可能还没有正确的签名
					index = concat(e.index, i)
					if obj != nil || e.multiples {
						return nil, index, false // 冲突
					}
					obj = m
					indirect = e.indirect
					continue // 我们不能有匹配字段或接口方法
				}
			}

			switch t := under(typ).(type) {
			case *Struct:
				// 寻找匹配字段并收集嵌入类型
				for i, f := range t.fields {
					if f.sameId(pkg, name) {
						assert(f.typ != nil)
						index = concat(e.index, i)
						if obj != nil || e.multiples {
							return nil, index, false // 冲突
						}
						obj = f
						indirect = e.indirect
						continue // 我们没有匹配的接口方法
					}
					// 收集嵌入式结构字段以搜索下一个
					// 较低的深度，但只有在我们还没有看到匹配的情况下（如果我们有匹配，它要么是所需的字段，要么是
					// 我们在同一深度上有名称冲突；无论是
					// 情况，我们都不需要进一步查看）。
					// 嵌入字段的格式始终为T或*T，其中
					// T是类型名。如果e.typ在
					// 这个深度出现多次，f.typ在下一个
					// 深度出现多次。
					if obj == nil && f.embedded {
						typ, isPtr := deref(f.typ)
						// TODO（gri）优化：忽略不能包含字段或方法的类型（只需要考虑命名、结构和
						// 接口类型）。
						next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples})
					}
				}

			case *Interface:
				// 查找匹配的方法（接口可能是类型参数）
				if i, m := t.typeSet().LookupMethod(pkg, name, foldCase); m != nil {
					assert(m.typ != nil)
					index = concat(e.index, i)
					if obj != nil || e.multiples {
						return nil, index, false // 冲突
					}
					obj = m
					indirect = e.indirect
				}
			}
		}

		if obj != nil {
			// 找到了一个潜在的匹配
			// 规范：“如果（类型）的方法集x 
			// 包含m，参数列表可以指定给参数
			// m的列表。如果x可寻址且&x的方法集包含m，则x.m（）
			// 是（&x）的缩写。m（）“.
			if f, _ := obj.(*Func); f != nil {
				// 确定方法是否有指针接收器
				if f.hasPtrRecv() && !indirect && !addressable {
					return nil, nil, true // 需要指针/可寻址接收器
				}
			}
			return
		}

		current = consolidateMultiples(next)
	}

	return nil, nil, false // 未找到
}

// embeddedType表示嵌入类型
type embeddedType struct {
	typ       Type
	index     []int // 嵌入字段索引，从深度0处的索引开始
	indirect  bool  // 如果设置，则指向该字段的路径上有一个指针
}

// consolidateMultiples将多个具有相同类型
// 的列表条目收集到一个标记为包含多个的条目中。结果是
// 合并列表。
func consolidateMultiples(list []embeddedType) []embeddedType {
	if len(list) <= 1 {
		return list // 最多一个条目-无需执行
	}

	n := 0                     // 条目数w/唯一类型
	prev := make(map[Type]int) // 之前看到的类型的索引
	for _, e := range list {
		if i, found := lookupType(prev, e.typ); found {
			list[i].multiples = true
			// 忽略此条目
		} else {
			prev[e.typ] = n
			list[n] = e
			n++
		}
	}
	return list[:n]
}

func lookupType(m map[Type]int, typ Type) (int, bool) {
	// 快速路径：可能这些类型相等
	if i, found := m[typ]; found {
		return i, true
	}

	for t, i := range m {
		if Identical(t, typ) {
			return i, true
		}
	}

	return 0, false
}

// 如果V实现T，MissingMethod返回（nil，false），否则，它将返回T所需的缺少的方法，以及它是缺少的还是类型错误。
// 
// 对于非接口类型V，或者如果设置了static，则如果V中存在T的所有
// 方法，则V实现T。否则（V为接口，未设置static 
// ），MissingMethod只检查在V中也存在
// 的T的方法是否具有匹配的类型（例如，对于类型断言x（T），其中
// x是接口类型V）。
// 
func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) {
	m, alt := (*Checker)(nil).missingMethod(V, T, static)
	// 如果替代方法与m同名，则仅报告错误的类型。
	return m, alt != nil && alt.name == m.name // alt！=零意味着m！=nil 
}

// missingMethod与missingMethod类似，但接受*检查器作为接收器。
// 如果通过导出的
// API调用missingMethod（例如missingMethod），即当所有方法都已被类型-
// 选中时，则接收器可能为零。
// 
// 如果在T上缺少一个方法，但在*T上找到了该方法，或者如果使用大小写折叠查找时在T上找到了一个方法
// 该替代方法将作为第二个结果返回
// 。
func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, alt *Func) {
	if T.NumMethods() == 0 {
		return
	}

	// V是一个接口
	if u, _ := under(V).(*Interface); u != nil {
		tset := u.typeSet()
		for _, m := range T.typeSet().methods {
			_, f := tset.LookupMethod(m.pkg, m.name, false)

			if f == nil {
				if !static {
					continue
				}
				return m, nil
			}

			if !Identical(f.typ, m.typ) {
				return m, f
			}
		}

		return
	}

	// V不是一个接口
	for _, m := range T.typeSet().methods {
		// TODO（gri）是否应该改为调用LookupFieldOrMethod（为什么不）？
		obj, _, _ := lookupFieldOrMethod(V, false, m.pkg, m.name, false)

		// 检查m是在*V上，还是在V上，带大小写折叠
		found := obj != nil
		if !found {
			// TODO（gri）而不是下面的新指针（V），我们可以设置“可寻址”参数吗？
			obj, _, _ = lookupFieldOrMethod(NewPointer(V), false, m.pkg, m.name, false)
			if obj == nil {
				obj, _, _ = lookupFieldOrMethod(V, false, m.pkg, m.name, true /* fold case */)
			}
		}

		// 我们必须有一个方法（不是结构字段）
		f, _ := obj.(*Func)
		if f == nil {
			return m, nil
		}

		// 方法可能尚未完全设置签名
		if check != nil {
			check.objDecl(f, nil)
		}

		if !found || !Identical(f.typ, m.typ) {
			return m, f
		}
	}

	return
}

// missingMethodReason返回一个字符串，给出缺少方法m的详细原因，
// 其中，V缺少m，但T需要m。它将原因放在括号中，
// 并可能包含更多have/want信息。如果非nil，alt是一个相关的
// 方法，它以某种方式匹配。它可能有正确的名称，但类型错误，或者
// 它可能有一个指针接收器，或者它可能有正确的名称，但大小写错误。
// 支票可能为零。
func (check *Checker) missingMethodReason(V, T Type, m, alt *Func) string {
	var mname string
	if check != nil && compilerErrorMessages {
		mname = m.Name() + " method"
	} else {
		mname = "method " + m.Name()
	}

	if alt != nil {
		if m.Name() != alt.Name() {
			return check.sprintf("(missing %s)\n\t\thave %s\n\t\twant %s",
				mname, check.funcString(alt), check.funcString(m))
		}

		if Identical(m.typ, alt.typ) {
			return check.sprintf("(%s has pointer receiver)", mname)
		}

		return check.sprintf("(wrong type for %s)\n\t\thave %s\n\t\twant %s",
			mname, check.funcString(alt), check.funcString(m))
	}

	if isInterfacePtr(V) {
		return "(" + check.interfacePtrError(V) + ")"
	}

	if isInterfacePtr(T) {
		return "(" + check.interfacePtrError(T) + ")"
	}

	return check.sprintf("(missing %s)", mname)
}

func isInterfacePtr(T Type) bool {
	p, _ := under(T).(*Pointer)
	return p != nil && IsInterface(p.base)
}

// 支票可能为零。
func (check *Checker) interfacePtrError(T Type) string {
	assert(isInterfacePtr(T))
	if p, _ := under(T).(*Pointer); isTypeParam(p.base) {
		return check.sprintf("type %s is pointer to type parameter, not type parameter", T)
	}
	return check.sprintf("type %s is pointer to interface, not interface", T)
}

// 支票可能为零。
func (check *Checker) funcString(f *Func) string {
	buf := bytes.NewBufferString(f.name)
	var qf Qualifier
	if check != nil {
		qf = check.qualifier
	}
	WriteSignature(buf, f.typ.(*Signature), qf)
	return buf.String()
}

// assertableTo报告类型V的值是否可以断言为类型T。
// 它返回（nil，false）作为肯定回答。否则，它将返回V所需的缺少的
// 方法，以及它是否缺少或只是具有错误的类型。
// 如果通过导出的API调用
// （例如assertableTo）调用assertableTo，即当所有方法都已进行类型检查时，则接收器可能为零。
// TODO（gri）将对该函数的调用替换为对newAssertableTo的调用。
func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) {
	// 如果T是接口
	// 规范：“如果T是接口类型，x.（T）断言
	// 动态类型x实现接口T。”
	if IsInterface(T) {
		return
	}
	// TODO（gri）修复通用接口
	return check.missingMethod(T, V, false)
}

// newAssetTable报告是否可以断言类型V的值具有类型T。
// 它还为当前仅允许处于约束位置的接口实现行为
// 我们尚未在规范中定义该行为。
func (check *Checker) newAssertableTo(V *Interface, T Type) error {
	// 如果T是接口
	// 规范：“如果T是接口类型，x.（T）断言
	// x的动态类型实现接口T。”
	if IsInterface(T) {
		return nil
	}
	return check.implements(T, V)
}

// 如果类型是*指针，则取消对其的引用，并返回其基值和true。
// 否则返回（typ，false）。
func deref(typ Type) (Type, bool) {
	if p, _ := typ.(*Pointer); p != nil {
		// p.base不应为零，但应保守
		if p.base == nil {
			if debug {
				panic("pointer with nil base type (possibly due to an invalid cyclic declaration)")
			}
			return Typ[Invalid], true
		}
		return p.base, true
	}
	return typ, false
}

// derefstructtptr dereferences typ，如果它是指向
// （已命名或未命名）结构并返回其基的（已命名或未命名）指针。否则返回typ。
func derefStructPtr(typ Type) Type {
	if p, _ := under(typ).(*Pointer); p != nil {
		if _, ok := under(p.base).(*Struct); ok {
			return p.base
		}
	}
	return typ
}

// concat返回连接列表和i的结果。
// 结果不与列表共享其底层数组。
func concat(list []int, i int) []int {
	var t []int
	t = append(t, list...)
	return append(t, i)
}

// fieldIndex返回具有匹配包和名称的字段的索引，或值<0。
func fieldIndex(fields []*Var, pkg *Package, name string) int {
	if name != "_" {
		for i, f := range fields {
			if f.sameId(pkg, name) {
				return i
			}
		}
	}
	return -1
}

// lookupMethod返回and方法的索引，并带有匹配的包和名称，或（-1，nil）。
// 如果foldCase为true，则如果方法名与大小写折叠相等，则认为它们相等。
func lookupMethod(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
	if name != "_" {
		for i, m := range methods {
			if (m.name == name || foldCase && strings.EqualFold(m.name, name)) && m.sameId(pkg, m.name) {
				return i, m
			}
		}
	}
	return -1, nil
}
